%% @doc 制御構文の評価器
%%
-module(templ_eval).
-export([eval_stmts/2, halt_cont/0]).


%% @spec eval_stmts(list(), function()) -> list()
%% @doc 構造化されたパース結果を評価する
%%
eval_stmts([], Cont) ->
	apply(Cont, [{ok,[]}]);
eval_stmts([{if_,Cond},TStmt,elseif,FStmt|R], Cont) ->
	eval_stmts([[{if_,Cond},TStmt,elseif,FStmt]|R], Cont);
eval_stmts([{if_,Cond},TStmt,else,FStmt|R], Cont) ->
	eval_stmts([[{if_,Cond},TStmt,elseif,FStmt]|R], Cont);
eval_stmts([{if_,Cond},Stmt|R], Cont) ->
	eval_stmts([[{if_,Cond},Stmt]|R], Cont);
eval_stmts([{let_,{expr,[{name,Pos,Name}]},{expr,ValTs}},Stmt|R], Cont) ->
	eval_stmts([[{let_,{expr,[{name,Pos,Name}]},{expr,ValTs}},Stmt]|R], Cont);
eval_stmts([{foreach,{expr,[{name,Pos,Name}]},{expr,ValTs}},Stmt|R], Cont) ->
	eval_stmts([[{foreach,{expr,[{name,Pos,Name}]},{expr,ValTs}},Stmt]|R], Cont);
eval_stmts([Stmt|R], Cont) ->
	eval(Stmt, eval_first_cont(R, Cont));
eval_stmts(Arg, Cont) ->
	erlang:error({badarg,{Arg,Cont}}).

eval([{if_,Cond},TStmt,elseif,FStmt], Cont) ->
	eval(Cond, test_cont(TStmt, FStmt, Cont));
eval([{if_,Cond},Stmt], Cont) ->
	eval(Cond, test_cont(Stmt, [], Cont));
eval([{let_,{expr,[{name,_,Name}]},{expr,ValTs}},Stmt], Cont) ->
	try templel_parse:parse(ValTs) of
		{ok,Value} -> 
			templ_dict:put_value(Name, Value),
			eval_stmts(Stmt, undef_cont(Name, Cont))
	catch
		error:_ ->
			eval_stmts("", Cont)
	end;
eval([{foreach,{expr,[{name,_,Name}]},{expr,ValTs}},Stmt], Cont) ->
	try templel_parse:parse(ValTs) of
		{ok,Values} -> 
			Stmts = lists:map(fun(Val) -> {foreach_, Name, Val, Stmt} end, Values),
			eval_stmts(Stmts, Cont)
	catch
		error:_ ->
			eval_stmts("", Cont)
	end;
eval({foreach_, Name, Value, Stmt}, Cont) ->
	templ_dict:put_value(Name, Value),
	eval_stmts(Stmt, undef_cont(Name, Cont));	
%eval(Text, Cont) when is_list(Text) ->
%	apply(Cont, [Text]);
eval({Sig,Text}, Cont) 
  when Sig =:= ok, is_list(Text);
	   Sig =:= error ->
	apply(Cont, [{Sig,Text}]);
eval({expr,Tokens}, Cont) ->
	%{ok,Result} = templel_parse:parse(Tokens),
	%eval(to_string(Result), Cont);
	try templel_parse:parse(Tokens) of
		{Sig,Result} -> eval({Sig,to_string(Result)}, Cont)
	catch
		error:X      -> eval({error,X}, Cont)
	end;
eval(Arg, Cont) ->
	erlang:error({badarg,{Arg,Cont}}).

%% @spec halt_cont() -> function()
%% @doc 末端の継続を得る
%%
halt_cont() ->
	fun (Val) -> 
			case Val of
				{ok, Text}   -> Text;
				{error, Why} -> erlang:error(Why)
			end
	end.

eval_first_cont(Rest, Cont) ->
	fun (Val) -> 
			case Val of
				{ok, Text}   -> eval_stmts(Rest, eval_rest_cont(Text, Cont));
				{error, Why} -> erlang:error(Why)
			end
	end.

eval_rest_cont(FText, Cont) ->
	fun (Val) -> 
			case Val of
				{ok, Text}   -> apply(Cont, [{ok, FText++Text}]);
				{error, Why} -> erlang:error(Why)
			end
	end.


test_cont(TStmt, FStmt, Cont) ->
	fun(Val) -> 
			case Val of
				{ok, Text}    -> 
					case Text of
						"true"  -> eval_stmts(TStmt, Cont);
						"false" -> eval_stmts(FStmt, Cont);
						"1"     -> eval_stmts(TStmt, Cont);
						"0"     -> eval_stmts(FStmt, Cont)
					end;
				{error, _Why} -> 
					eval_stmts(FStmt, Cont)
			end
	end.
	
undef_cont(Name, Cont) ->
	fun(Val) -> 
			case Val of
				{ok, _Text}   -> templ_dict:remove_value(Name);
				{error, _Why} -> none
			end,
			apply(Cont, [Val])
	end.
	

%% @spec to_string(atom()|integer()|term()) -> list()
%% @doc 文字列に変換する
%%
to_string(Expr) when is_atom(Expr) ->
	atom_to_list(Expr);
to_string(Expr) when is_integer(Expr) ->
	integer_to_list(Expr);
to_string(Expr) ->
	Expr.
